/* Copyright (c) 2006, 2009, Carl Burch. License information is located in the
 * com.cburch.logisim.Main source code and at www.cburch.com/logisim/. */
 
package com.cburch.logisim.gui.main;

import java.awt.Color;
import java.awt.Graphics;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.HashSet;

import com.cburch.logisim.circuit.CircuitEvent;
import com.cburch.logisim.circuit.CircuitListener;
import com.cburch.logisim.circuit.ComponentAction;
import com.cburch.logisim.circuit.Wire;
import com.cburch.logisim.comp.Component;
import com.cburch.logisim.comp.ComponentDrawContext;
import com.cburch.logisim.data.AttributeSet;
import com.cburch.logisim.data.Bounds;
import com.cburch.logisim.data.Location;
import com.cburch.logisim.proj.Project;
import com.cburch.logisim.proj.ProjectEvent;
import com.cburch.logisim.proj.ProjectListener;
import com.cburch.logisim.tools.CustomHandles;

public class Selection extends SelectionBase {
    public static class Event {
        Object source;
        Event(Object source) { this.source = source; }
        public Object getSource() { return source; }
    }

    public static interface Listener {
        public void selectionChanged(Selection.Event event);
    }
    
    private class MyListener implements ProjectListener, CircuitListener {
        private HashSet justRemoved = new HashSet();
        
        public void projectChanged(ProjectEvent event) {
            if(event.getAction() == ProjectEvent.ACTION_COMPLETED) {
                Object actRaw = event.getData();
                if(actRaw instanceof ComponentAction) {
                    ComponentAction act = (ComponentAction) actRaw;
                    HashSet ws = justRemoved;
                    justRemoved = new HashSet();
                    ws.addAll(getAnchoredComponents());
                    addOverlapsAdds(ws, act.getRemovals(), act.getAdditions());
                    addOverlapsAdds(ws, act.getIncidentalRemovals(), act.getIncidentalAdditions());
                    addOverlapsAdds(ws, act.getIncidentalRemovals(), act.getAdditions());
                }
            } else if(event.getAction() != ProjectEvent.ACTION_SELECTION) {
                justRemoved.clear();
            }
        }
        
        private void addOverlapsAdds(Collection ws, Collection removals,
                Collection additions) {
            for(Iterator it = removals.iterator(); it.hasNext(); ) {
                Object wRaw = it.next();
                if(wRaw instanceof Wire && ws.contains(wRaw)) {
                    addAllOverlaps((Wire) wRaw, additions);
                }
            }
        }
        
        private void addAllOverlaps(Wire w0, Collection from) {
            for(Iterator it = from.iterator(); it.hasNext(); ) {
                Object w1Raw = it.next();
                if(w1Raw instanceof Wire) {
                    Wire w1 = (Wire) w1Raw;
                    if(w0.overlaps(w1)) {
                        add(w1);
                    }
                }
            }
        }

        public void circuitChanged(CircuitEvent event) {
            if(event.getAction() == CircuitEvent.ACTION_REMOVE) {
                Component comp = (Component) event.getData();
                if(comp instanceof Wire && selected.contains(comp)) {
                    justRemoved.add(comp);
                }
                remove(comp);
            } else if(event.getAction() == CircuitEvent.ACTION_CLEAR) {
                clear(false);
            }
        }       
    }

    private MyListener myListener;
    private boolean isVisible = true;

    public Selection(Project proj) {
        super(proj);
        
        myListener = new MyListener();
        proj.addProjectListener(myListener);
        proj.addCircuitListener(myListener);
    }

    //
    // query methods
    //
    public boolean isEmpty() {
        return selected.isEmpty() && lifted.isEmpty();
    }

    public boolean equals(Object other) {
        if(!(other instanceof Selection)) return false;
        Selection otherSelection = (Selection) other;
        return this.selected.equals(otherSelection.selected)
            && this.lifted.equals(otherSelection.lifted);
    }

    public Collection getComponents() {
        return unionSet;
    }
    
    public Collection getAnchoredComponents() {
        return selected;
    }
    
    public Collection getFloatingComponents() {
        return lifted;
    }

    public Collection getHiddenComponents() {
        return isVisible ? Collections.EMPTY_SET : unionSet;
    }

    public Collection getComponentsContaining(Location query) {
        HashSet ret = new HashSet();
        for(Iterator it = unionSet.iterator(); it.hasNext(); ) {
            Component comp = (Component) it.next();
            if(comp.contains(query)) ret.add(comp);
        }
        return ret;
    }

    public Collection getComponentsContaining(Location query, Graphics g) {
        HashSet ret = new HashSet();
        for(Iterator it = unionSet.iterator(); it.hasNext(); ) {
            Component comp = (Component) it.next();
            if(comp.contains(query, g)) ret.add(comp);
        }
        return ret;
    }

    public Collection getComponentsWithin(Bounds bds) {
        HashSet ret = new HashSet();
        for(Iterator it = unionSet.iterator(); it.hasNext(); ) {
            Component comp = (Component) it.next();
            if(bds.contains(comp.getBounds())) ret.add(comp);
        }
        return ret;
    }

    public Collection getComponentsWithin(Bounds bds, Graphics g) {
        HashSet ret = new HashSet();
        for(Iterator it = unionSet.iterator(); it.hasNext(); ) {
            Component comp = (Component) it.next();
            if(bds.contains(comp.getBounds(g))) ret.add(comp);
        }
        return ret;
    }

    public boolean contains(Component comp) {
        return unionSet.contains(comp);
    }

    //
    // graphics methods
    //
    public void setVisible(boolean value) {
        isVisible = value;
    }

    public void draw(ComponentDrawContext context) {
        if(isVisible) {
            Graphics g = context.getGraphics();

            for(Iterator it = lifted.iterator(); it.hasNext(); ) {
                Component c = (Component) it.next();
                Location loc = c.getLocation();

                Graphics g_new = g.create();
                context.setGraphics(g_new);
                c.getFactory().drawGhost(context, Color.GRAY,
                        loc.getX(), loc.getY(), c.getAttributeSet());
                g_new.dispose();
            }

            for(Iterator it = unionSet.iterator(); it.hasNext(); ) {
                Component comp = (Component) it.next();
                if(!suppressHandles.contains(comp)) {
                    Graphics g_new = g.create();
                    context.setGraphics(g_new);
                    CustomHandles handler
                        = (CustomHandles) comp.getFeature(CustomHandles.class);
                    if(handler == null) {
                        context.drawHandles(comp);
                    } else {
                        handler.drawHandles(context);
                    }
                    g_new.dispose();
                }
            }

            context.setGraphics(g);
        }
    }

    public void drawGhostsShifted(ComponentDrawContext context,
            int dx, int dy) {
        if(shouldSnap()) {
            dx = Canvas.snapXToGrid(dx);
            dy = Canvas.snapYToGrid(dy);
        }
        Graphics g = context.getGraphics();
        for(Iterator it = unionSet.iterator(); it.hasNext(); ) {
            Component comp = (Component) it.next();
            AttributeSet attrs = comp.getAttributeSet();
            Location loc = comp.getLocation();
            int x = loc.getX() + dx;
            int y = loc.getY() + dy;
            context.setGraphics(g.create());
            comp.getFactory().drawGhost(context, Color.gray, x, y, attrs);
            context.getGraphics().dispose();
        }
        context.setGraphics(g);
    }
    
    public void print() {
        System.err.println(" isVisible: " + isVisible); //OK
        super.print();
    }

}
